This notebook mirrors mesh.stats.html

In [1]:
from pathlib import Path

from ipyniivue import download_dataset

BASE_API_URL = "https://niivue.com/demos/images/"
DATA_FOLDER = Path("images")

# Download data for example
download_dataset(
    BASE_API_URL,
    DATA_FOLDER,
    files=[
        "BrainMesh_ICBM152.lh.curv",
        "BrainMesh_ICBM152.lh.motor.mz3",
        "BrainMesh_ICBM152.lh.mz3",
    ],
)
BrainMesh_ICBM152.lh.curv already exists.
BrainMesh_ICBM152.lh.motor.mz3 already exists.
BrainMesh_ICBM152.lh.mz3 already exists.
Dataset downloaded successfully to images.
In [2]:
import ipywidgets as widgets

from ipyniivue import ColormapType, Mesh, MeshLayer, NiiVue, SliceType

# 1. Initialize NiiVue
nv = NiiVue()
nv.opts.is_colorbar = True
nv.opts.back_color = (0.3, 0.3, 0.3, 1.0)
nv.opts.show_3d_crosshair = True
nv.opts.slice_type = SliceType.RENDER

# 2. Define Layers
# Layer 0: Curvature (Gray)
layer0 = MeshLayer(
    path=DATA_FOLDER / "BrainMesh_ICBM152.lh.curv",
    colormap="gray",
    cal_min=0.3,
    cal_max=0.5,
    opacity=0.7,
)
layer0.colorbar_visible = False  # notice how this is not added in kwargs

# Layer 1: Motor Map (Warm/Winter)
layer1 = MeshLayer(
    path=DATA_FOLDER / "BrainMesh_ICBM152.lh.motor.mz3",
    cal_min=1.5,
    cal_max=5.0,
    colormap="warm",
    colormap_negative="winter",
    use_negative_cmap=True,
    opacity=0.7,
)

# 3. Load Mesh with Layers
mesh = Mesh(
    path=DATA_FOLDER / "BrainMesh_ICBM152.lh.mz3",
    layers=[layer0, layer1],
    rgba255=[255, 255, 255, 255],
)

nv.meshes = [mesh]

# 4. Define Widgets & Interactions

# --- Sliders ---

# Curvature Opacity (Layer 0)
curve_slider = widgets.FloatSlider(
    value=0.7, min=0, max=1.0, step=0.1, description="Curvature"
)


def on_curve_change(change):
    """Handle curve change."""
    layer0.opacity = change["new"]


curve_slider.observe(on_curve_change, names="value")

# Threshold & Range (Layer 1 cal_min/cal_max)
thresh_slider = widgets.FloatSlider(
    value=1.5, min=0.1, max=10.0, step=0.1, description="Threshold"
)
range_slider = widgets.FloatSlider(
    value=3.5, min=0.1, max=9.0, step=0.1, description="Range"
)


def update_thresh_range(_):
    """Update thresh range."""
    mn = thresh_slider.value
    mx = mn + range_slider.value
    layer1.cal_min = mn
    layer1.cal_max = mx


thresh_slider.observe(update_thresh_range, names="value")
range_slider.observe(update_thresh_range, names="value")

# Opacity (Layer 1)
opacity_slider = widgets.FloatSlider(
    value=0.7, min=0, max=1.0, step=0.1, description="Opacity"
)


def on_opacity_change(change):
    """Handle opacity change."""
    layer1.opacity = change["new"]


opacity_slider.observe(on_opacity_change, names="value")


# --- Checkboxes ---

# Negative Colors
negative_check = widgets.Checkbox(value=True, description="NegativeColors")


def on_negative_change(change):
    """Handle negative change."""
    layer1.use_negative_cmap = change["new"]
    nv.refresh_colormaps()


negative_check.observe(on_negative_change, names="value")

# Diverging Colors
diverge_check = widgets.Checkbox(value=False, description="Diverging Colors")


def on_diverge_change(change):
    """Handle diverge change."""
    if change["new"]:
        layer1.colormap = "green2orange"
        layer1.colormap_negative = "green2cyan"
    else:
        layer1.colormap = "warm"
        layer1.colormap_negative = "winter"


diverge_check.observe(on_diverge_change, names="value")

# Dark Background
dark_check = widgets.Checkbox(value=True, description="Dark")


def on_dark_change(change):
    """Handle background change."""
    v = 0.3 if change["new"] else 1.0
    nv.opts.back_color = (v, v, v, 1.0)


dark_check.observe(on_dark_change, names="value")


# --- Dropdowns ---

# Colormap Type
cmap_type_drop = widgets.Dropdown(
    options=[
        ("restrict colorbar to range", ColormapType.MIN_TO_MAX.value),
        (
            "colorbar from 0, transparent sub",
            ColormapType.ZERO_TO_MAX_TRANSPARENT_BELOW_MIN.value,
        ),
        (
            "colorbar from 0, translucent sub",
            ColormapType.ZERO_TO_MAX_TRANSLUCENT_BELOW_MIN.value,
        ),
    ],
    value=ColormapType.MIN_TO_MAX.value,
    description="Cmap Type",
)


def on_cmap_type_change(change):
    """Handle cmap type change."""
    layer1.colormap_type = ColormapType(change["new"])


cmap_type_drop.observe(on_cmap_type_change, names="value")

# Outline Border
outline_drop = widgets.Dropdown(
    options=[("no outline", 0), ("opaque outline", 1), ("black outline", -1)],
    value=0,
    description="Outline",
)


def on_outline_change(change):
    """Handle outline change."""
    layer1.outline_border = float(change["new"])


outline_drop.observe(on_outline_change, names="value")

# Mesh Base Color
color_drop = widgets.Dropdown(
    options=["white", "red", "green", "blue"], value="white", description="Color"
)


def on_color_change(change):
    """Handle color change."""
    c = change["new"]
    rgb = [255, 255, 255]
    if c == "red":
        rgb = [255, 32, 32]
    elif c == "green":
        rgb = [0, 222, 32]
    elif c == "blue":
        rgb = [92, 128, 255]

    mesh.rgba255 = [*rgb, 255]  # Add alpha


color_drop.observe(on_color_change, names="value")

# Mesh Shaders
shader_names = nv.mesh_shader_names()
shader_dropdown = widgets.Dropdown(
    options=shader_names, value="Phong", description="Shader"
)


def on_shader_change(change):
    """Handle shader change."""
    nv.set_mesh_shader(mesh.id, change["new"])


shader_dropdown.observe(on_shader_change, names="value")


# --- Layout ---
ui = widgets.VBox(
    [
        widgets.HBox([curve_slider, thresh_slider]),
        widgets.HBox([range_slider, opacity_slider]),
        widgets.HBox([negative_check, diverge_check, dark_check]),
        widgets.HBox([cmap_type_drop, outline_drop]),
        widgets.HBox([color_drop, shader_dropdown]),
        nv,
    ]
)

# Display All
ui
Out[2]: